O'zbek

TypeScript generiklari boʻyicha qoʻllanma: sintaksis, afzalliklar va global dasturlashda murakkab maʼlumotlar turlari bilan ishlashning eng yaxshi usullari.

TypeScript Generics: Ishonchli Ilovalar uchun Murakkab Ma'lumot Turlarini O'zlashtirish

JavaScript'ning ustki to'plami bo'lgan TypeScript, dasturchilarga statik tiplash orqali yanada ishonchli va qo'llab-quvvatlanadigan kod yozish imkonini beradi. Uning eng kuchli xususiyatlaridan biri bu generiklar bo'lib, ular sizga turli xil ma'lumotlar turlari bilan ishlay oladigan kod yozishga imkon beradi, shu bilan birga tip xavfsizligini saqlab qoladi. Ushbu qo'llanma TypeScript generiklarini har tomonlama o'rganishni ta'minlaydi, bunda global dasturiy ta'minot ishlab chiqish kontekstida murakkab ma'lumotlar turlariga qo'llanilishiga e'tibor qaratiladi.

Generiklar nima?

Generiklar turli tiplar bilan ishlay oladigan qayta ishlatiladigan kod yozish usulini taqdim etadi. Qo'llab-quvvatlamoqchi bo'lgan har bir tip uchun alohida funksiyalar yoki sinflar yozish o'rniga, siz tip parametrlaridan foydalanadigan bitta funksiya yoki sinf yozishingiz mumkin. Bu tip parametrlari funksiya yoki sinf chaqirilganda yoki yaratilganda ishlatiladigan haqiqiy tiplar uchun joy egallovchilardir. Bu, ayniqsa, murakkab ma'lumotlar tuzilmalari bilan ishlashda foydalidir, chunki bu tuzilmalar ichidagi ma'lumotlar tipi o'zgarishi mumkin.

Generiklardan foydalanishning afzalliklari

Generiklarning asosiy sintaksisi

Generiklarning asosiy sintaksisi tip parametrlarini e'lon qilish uchun burchakli qavslardan (< >) foydalanishni o'z ichiga oladi. Bu tip parametrlari odatda T, K, V va hokazo deb nomlanadi, lekin siz har qanday yaroqli identifikatorni ishlatishingiz mumkin. Quyida generik funksiyaning oddiy misoli keltirilgan:


function identity<T>(arg: T): T {
  return arg;
}

let myString: string = identity<string>("hello");
let myNumber: number = identity<number>(123);
let myBoolean: boolean = identity<boolean>(true);

console.log(myString); // Output: hello
console.log(myNumber); // Output: 123
console.log(myBoolean); // Output: true

Ushbu misolda <T> T nomli tip parametrini e'lon qiladi. identity funksiyasi T tipidagi argumentni qabul qiladi va T tipidagi qiymatni qaytaradi. Funksiyani chaqirishda siz tip parametrini aniq belgilashingiz mumkin (masalan, identity<string>) yoki TypeScript'ga uni argument tipiga qarab avtomatik aniqlashiga ruxsat berishingiz mumkin.

Murakkab ma'lumotlar turlari bilan ishlash

Generiklar massivlar, obyektlar va interfeyslar kabi murakkab ma'lumotlar turlari bilan ishlashda ayniqsa qimmatli bo'ladi. Keling, ba'zi keng tarqalgan holatlarni ko'rib chiqaylik:

Generik massivlar

Siz generiklardan turli tiplardagi massivlar bilan ishlaydigan funksiyalar yoki sinflar yaratish uchun foydalanishingiz mumkin:


function arrayToString<T>(arr: T[]): string {
  return arr.join(", ");
}

let numberArray: number[] = [1, 2, 3, 4, 5];
let stringArray: string[] = ["apple", "banana", "cherry"];

console.log(arrayToString(numberArray)); // Output: 1, 2, 3, 4, 5
console.log(arrayToString(stringArray)); // Output: apple, banana, cherry

Bu yerda arrayToString funksiyasi T[] tipidagi massivni qabul qiladi va massivning matnli ko'rinishini qaytaradi. Ushbu funksiya har qanday tipdagi massivlar bilan ishlaydi, bu esa uni yuqori darajada qayta ishlatiladigan qiladi.

Generik obyektlar

Generiklar, shuningdek, turli shakldagi obyektlar bilan ishlaydigan funksiyalar yoki sinflarni aniqlash uchun ham ishlatilishi mumkin:


interface Person {
  name: string;
  age: number;
  country: string; // Added country for global context
}

interface Product {
  id: number;
  name: string;
  price: number;
  currency: string; // Added currency for global context
}

function displayInfo<T extends { name: string }>(item: T): void {
  console.log(`Name: ${item.name}`);
}

let person: Person = { name: "Alice", age: 30, country: "USA" };
let product: Product = { id: 1, name: "Laptop", price: 1200, currency: "USD" };

displayInfo(person); // Output: Name: Alice
displayInfo(product); // Output: Name: Laptop

Ushbu misolda displayInfo funksiyasi T tipidagi obyektni qabul qiladi, u matn tipidagi name xususiyatiga ega bo'lishi kerak. extends { name: string } qismi cheklov bo'lib, u T tip parametri uchun minimal talablarni belgilaydi. Bu funksiyaning name xususiyatiga xavfsiz murojaat qilishini ta'minlaydi.

Generiklarning ilg'or qo'llanilishi

TypeScript generiklari yanada moslashuvchan va kuchli kod yaratishga imkon beruvchi ilg'or xususiyatlarni taklif qiladi. Keling, ushbu xususiyatlarning ba'zilarini ko'rib chiqaylik:

Bir nechta tip parametrlari

Siz bir nechta tip parametrlari bilan funksiyalar yoki sinflarni aniqlashingiz mumkin:


function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

interface Name {
  firstName: string;
}

interface Age {
  age: number;
}

const person: Name = { firstName: "Bob" };
const details: Age = { age: 42 };

const merged = merge(person, details);
console.log(merged.firstName); // Output: Bob
console.log(merged.age); // Output: 42

merge funksiyasi T va U tipidagi ikkita obyektni qabul qiladi va ikkala obyektning xususiyatlarini o'z ichiga olgan yangi obyektni qaytaradi. Bu turli manbalardan olingan ma'lumotlarni birlashtirishning kuchli usulidir.

Generik cheklovlar

Yuqorida ko'rsatilganidek, cheklovlar generik tip parametri bilan ishlatilishi mumkin bo'lgan tiplarni cheklashga imkon beradi. Bu generik kodning belgilangan tiplar bilan xavfsiz ishlashini ta'minlaydi.


interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

loggingIdentity([1, 2, 3]); // Output: 3
loggingIdentity("hello"); // Output: 5
// loggingIdentity(123); // Error: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.

loggingIdentity funksiyasi T tipidagi argumentni qabul qiladi, u raqam tipidagi length xususiyatiga ega bo'lishi kerak. Bu funksiyaning length xususiyatiga xavfsiz murojaat qilishini ta'minlaydi.

Generik sinflar

Generiklar sinflar bilan ham ishlatilishi mumkin:


class DataStorage<T> {
  private data: T[] = [];

  addItem(item: T) {
    this.data.push(item);
  }

  removeItem(item: T) {
    this.data = this.data.filter(d => d !== item);
  }

  getItems(): T[] {
    return [...this.data];
  }
}

const textStorage = new DataStorage<string>();
textStorage.addItem("apple");
textStorage.addItem("banana");
textStorage.removeItem("apple");
console.log(textStorage.getItems()); // Output: [ 'banana' ]

const numberStorage = new DataStorage<number>();
numberStorage.addItem(1);
numberStorage.addItem(2);
numberStorage.removeItem(1);
console.log(numberStorage.getItems()); // Output: [ 2 ]

DataStorage sinfi har qanday T tipidagi ma'lumotlarni saqlashi mumkin. Bu sizga tip xavfsizligiga ega bo'lgan qayta ishlatiladigan ma'lumotlar tuzilmalarini yaratishga imkon beradi.

Generik interfeyslar

Generik interfeyslar turli tiplar bilan ishlay oladigan shartnomalarni (kontraktlarni) aniqlash uchun foydalidir. Masalan:


interface Result<T, E> {
  success: boolean;
  data?: T;
  error?: E;
}

interface User {
  id: number;
  username: string;
  email: string;
}

interface ErrorMessage {
  code: number;
  message: string;
}

function fetchUser(id: number): Result<User, ErrorMessage> {
  if (id === 1) {
    return { success: true, data: { id: 1, username: "john.doe", email: "john.doe@example.com" } };
  } else {
    return { success: false, error: { code: 404, message: "User not found" } };
  }
}

const userResult = fetchUser(1);
if (userResult.success) {
  console.log(userResult.data.username);
} else {
  console.log(userResult.error.message);
}

Result interfeysi operatsiya natijasini ifodalash uchun generik tuzilmani aniqlaydi. U T tipidagi ma'lumotlarni yoki E tipidagi xatolikni o'z ichiga olishi mumkin. Bu asinxron operatsiyalarni yoki muvaffaqiyatsiz tugashi mumkin bo'lgan operatsiyalarni boshqarish uchun keng tarqalgan usuldir.

Yordamchi tiplar va Generiklar

TypeScript generiklar bilan yaxshi ishlaydigan bir nechta o'rnatilgan yordamchi tiplarni taqdim etadi. Ushbu yordamchi tiplar sizga tiplarni kuchli usullar bilan o'zgartirish va boshqarishga yordam beradi.

Partial<T>

Partial<T> T tipining barcha xususiyatlarini ixtiyoriy qiladi:


interface Person {
  name: string;
  age: number;
}

type PartialPerson = Partial<Person>;

const partialPerson: PartialPerson = { name: "Alice" }; // Valid

Readonly<T>

Readonly<T> T tipining barcha xususiyatlarini faqat o'qish uchun (readonly) qiladi:


interface Person {
  name: string;
  age: number;
}

type ReadonlyPerson = Readonly<Person>;

const readonlyPerson: ReadonlyPerson = { name: "Bob", age: 42 };
// readonlyPerson.age = 43; // Error: Cannot assign to 'age' because it is a read-only property.

Pick<T, K>

Pick<T, K> T tipidan K xususiyatlar to'plamini tanlaydi:


interface Person {
  name: string;
  age: number;
  email: string;
}

type NameAndAge = Pick<Person, "name" | "age">;

const nameAndAge: NameAndAge = { name: "Charlie", age: 28 };

Omit<T, K>

Omit<T, K> T tipidan K xususiyatlar to'plamini olib tashlaydi:


interface Person {
  name: string;
  age: number;
  email: string;
}

type PersonWithoutEmail = Omit<Person, "email">;

const personWithoutEmail: PersonWithoutEmail = { name: "David", age: 35 };

Record<K, T>

Record<K, T> kalitlari K va qiymatlari T tipida bo'lgan tip yaratadi:


type CountryCodes = "US" | "CA" | "UK" | "DE" | "FR" | "JP" | "CN" | "IN" | "BR" | "AU"; // Expanded list for global context
type Currency = "USD" | "CAD" | "GBP" | "EUR" | "JPY" | "CNY" | "INR" | "BRL" | "AUD"; // Expanded list for global context

type CurrencyMap = Record<CountryCodes, Currency>;

const currencyMap: CurrencyMap = {
  "US": "USD",
  "CA": "CAD",
  "UK": "GBP",
  "DE": "EUR",
  "FR": "EUR",
  "JP": "JPY",
  "CN": "CNY",
  "IN": "INR",
  "BR": "BRL",
  "AU": "AUD",
};

Xaritalangan tiplar (Mapped Types)

Xaritalangan tiplar mavjud tiplarni ularning xususiyatlari bo'yicha takrorlash orqali o'zgartirishga imkon beradi. Bu mavjud tiplar asosida yangi tiplar yaratishning kuchli usulidir. Masalan, siz boshqa tipning barcha xususiyatlarini faqat o'qish uchun (readonly) qiladigan tip yaratishingiz mumkin:


interface Person {
  name: string;
  age: number;
}

type ReadonlyPerson = {
  readonly [K in keyof Person]: Person[K];
};

const readonlyPerson: ReadonlyPerson = { name: "Eve", age: 25 };
// readonlyPerson.age = 26; // Error: Cannot assign to 'age' because it is a read-only property.

Ushbu misolda [K in keyof Person] Person interfeysining barcha kalitlari bo'yicha takrorlanadi va Person[K] har bir xususiyatning tipiga murojaat qiladi. readonly kalit so'zi har bir xususiyatni faqat o'qish uchun qiladi.

Shartli tiplar (Conditional Types)

Shartli tiplar shartlarga asoslanib tiplarni aniqlashga imkon beradi. Bu turli xil stsenariylarga moslashadigan tiplar yaratishning kuchli usulidir.


type NonNullable<T> = T extends null | undefined ? never : T;

type MaybeString = string | null | undefined;
type StringType = NonNullable<MaybeString>; // string

function getValue<T>(value: T): NonNullable<T> {
  if (value == null) { // Handles both null and undefined
    throw new Error("Value cannot be null or undefined");
  }
  return value as NonNullable<T>;
}

try {
  const validValue = getValue("hello");
  console.log(validValue.toUpperCase()); // Output: HELLO

  const invalidValue = getValue(null); // This will throw an error
  console.log(invalidValue); // This line will not be reached
} catch (error: any) {
  console.error(error.message); // Output: Value cannot be null or undefined
}

Ushbu misolda NonNullable<T> tipi T ning null yoki undefined ekanligini tekshiradi. Agar shunday bo'lsa, u never ni qaytaradi, bu esa bu tipga ruxsat etilmaganligini anglatadi. Aks holda, u T ni qaytaradi. Bu sizga null bo'lmasligi kafolatlangan tiplar yaratishga imkon beradi.

Generiklardan foydalanishning eng yaxshi amaliyotlari

Generiklardan foydalanishda yodda tutish kerak bo'lgan ba'zi eng yaxshi amaliyotlar:

Global kontekstdagi misollar

Keling, generiklarning global kontekstda qanday ishlatilishi mumkinligiga oid ba'zi misollarni ko'rib chiqaylik:

Valyuta konvertatsiyasi


interface ConversionRate {
  rate: number;
  fromCurrency: string;
  toCurrency: string;
}

function convertCurrency<T extends ConversionRate>(amount: number, rate: T): number {
  return amount * rate.rate;
}

const usdToEurRate: ConversionRate = { rate: 0.85, fromCurrency: "USD", toCurrency: "EUR" };
const amountInUSD = 100;
const amountInEUR = convertCurrency(amountInUSD, usdToEurRate);
console.log(`${amountInUSD} USD is equal to ${amountInEUR} EUR`); // Output: 100 USD is equal to 85 EUR

Sana formatlash


interface DateFormatOptions {
  locale: string;
  options: Intl.DateTimeFormatOptions;
}

function formatDate<T extends DateFormatOptions>(date: Date, format: T): string {
  return date.toLocaleDateString(format.locale, format.options);
}

const currentDate = new Date();

const usDateFormat: DateFormatOptions = { locale: "en-US", options: { year: 'numeric', month: 'long', day: 'numeric' } };
const germanDateFormat: DateFormatOptions = { locale: "de-DE", options: { year: 'numeric', month: 'long', day: 'numeric' } };
const japaneseDateFormat: DateFormatOptions = { locale: "ja-JP", options: { year: 'numeric', month: 'long', day: 'numeric' } };

console.log("US Date: " + formatDate(currentDate, usDateFormat));
console.log("German Date: " + formatDate(currentDate, germanDateFormat));
console.log("Japanese Date: " + formatDate(currentDate, japaneseDateFormat));

Tarjima xizmati


interface Translation {
  [key: string]: string; // Allows for dynamic language keys
}

interface LanguageData<T extends Translation> {
  languageCode: string;
  translations: T;
}

const englishTranslations: Translation = {
  "hello": "Hello",
  "goodbye": "Goodbye",
  "welcome": "Welcome to our website!"
};

const spanishTranslations: Translation = {
  "hello": "Hola",
  "goodbye": "Adiós",
  "welcome": "¡Bienvenido a nuestro sitio web!"
};

const frenchTranslations: Translation = {
  "hello": "Bonjour",
  "goodbye": "Au revoir",
  "welcome": "Bienvenue sur notre site web !"
};


const languageData: LanguageData<typeof englishTranslations>[] = [
  {languageCode: "en", translations: englishTranslations },
  {languageCode: "es", translations: spanishTranslations },
  {languageCode: "fr", translations: frenchTranslations}
];

function translate<T extends Translation>(key: string, languageCode: string, languageData: LanguageData<T>[]): string {
  const lang = languageData.find(lang => lang.languageCode === languageCode);
  if (!lang) {
    return `Translation for ${key} in ${languageCode} not found.`;
  }
  return lang.translations[key] || `Translation for ${key} not found.`;
}

console.log(translate("hello", "en", languageData)); // Output: Hello
console.log(translate("hello", "es", languageData)); // Output: Hola
console.log(translate("welcome", "fr", languageData)); // Output: Bienvenue sur notre site web !
console.log(translate("missingKey", "de", languageData)); // Output: Translation for missingKey in de not found.

Xulosa

TypeScript generiklari murakkab ma'lumotlar turlari bilan ishlay oladigan, qayta ishlatiladigan, tip xavfsizligiga ega kod yozish uchun kuchli vositadir. Generiklarning asosiy sintaksisi, ilg'or xususiyatlari va eng yaxshi amaliyotlarini tushunish orqali siz TypeScript ilovalaringizning sifati va qo'llab-quvvatlanishini sezilarli darajada yaxshilashingiz mumkin. Global auditoriya uchun ilovalar ishlab chiqishda generiklar turli xil ma'lumotlar formatlari va madaniy an'analarni boshqarishga yordam beradi, bu esa hamma uchun uzluksiz foydalanuvchi tajribasini ta'minlaydi.